#pragma once

namespace Melting
{  

#ifdef _DEBUG
	#define ASSERT_DATA_AVAILABLE()			MB_ASSERT( IsAvailable( _rID ), "The TResource ID '%s' does not exist!", _rID.c_str() )
	#define ASSERT_DATA_NOT_AVAILABLE()		MB_ASSERT( !IsAvailable( _rID ), "The TResource ID '%s' already exists!", _rID.c_str() )		
	
#else
	#define ASSERT_DATA_AVAILABLE()			MB_ASSERT( IsAvailable( _rID ), "The TResource ID '%d' does not exist!", _rID )
	#define ASSERT_DATA_NOT_AVAILABLE()		MB_ASSERT( !IsAvailable( _rID ), "The TResource ID '%d' already exists!", _rID )
#endif

	/**
		Template class to manager Resources
	*/
	template < class TResource, class TResourceID, class TResourceInstance >
	class ResourceManager
	{
	public:
	
		/// CTOR - Specify the base directory containting all TResources of type TResource
		ResourceManager( const String&  _strBaseDirectory )  ;
		
		/// DTOR
		virtual ~ResourceManager( void );

		/// Get the base directory containting all TResources of type TResource
		inline const String&			GetBaseDirectory		( void ) const;
		
		
		/// Specify wether the ResourceManager should automatically try to destroy a TResource when we detroy an TResourceInstance
		inline void						SetAutoClean			( bool _bAutoClean );

		/// Specify the TResource that should be used by default
		inline void						SetDefaultResource	( const TResourceID& _rID );
		 
		/// Get the TResource associated to the given TResourceID - 
		inline TResource& 				Get					( const TResourceID& _rID ) ;

		/// PROVIDE IT - Create the TResource associated to the given TResourceID
		virtual TResource&				Create				( const TResourceID& _rID );
		/// PROVIDE IT - Destroy the TResource associated to the given TResourceID
		virtual void					Destroy				( const TResourceID& _rID );
		
		
		/// Create the TResource associated to the given TResourceID, and Register it !
		TResource&						CreateAndRegister	( const TResourceID& _rID ) ; 
		
		/// Destroy the TResource associated to the given TResourceID 
		void							DestroyAndUnregister ( const TResourceID& _rID ) ;
		 
		
		/// Get the TResource associated to the given TResourceID - Create (and Register) it if it not already available !
		TResource& 						GetOrCreate			( const TResourceID& _rID );
					
		/// Create an instance of the given TResource
		TResourceInstance& 				CreateInstance		( const ResourceID& _rID );
		/// Destroy the given instance
		void 							DestroyInstance		( TResourceInstance& _rInstance );


		/// Destoy all registered TResource 
		virtual void 					DestroyAll			( void );
		
		/// Is the TResource already available ?
		inline bool 					IsAvailable			( const TResourceID& _rID ) const;
	 
	
		/// Register the given TResource with its associated TResourceID, adding it into the collection of available TResource
		inline void 					Register			( const TResourceID& _rID, TResource& _rResource );
		
		/// Unregister the TResource associated to the given TResourceID, removing it from the collection of available TResource - !! No memory deallocation is done !
		inline void 					Unregister			( const TResourceID& _rID );
		
		/// NDAL : DO NEEEEEVAAAAAAAAA USE THIS !!!!!    --- std::map traversal !!!!
		void 							Unregister			( TResource& _rResource );
		
	 	/// Clean every resource not currently in use !
	 	void							DestroyAndUnregisterAllUnusedResources( void );

		/// Reload all TResource that have been modified since there last load
		ULONG							ReloadModifiedResources( void );

		
		/// Specify a directory for unoptimized resources, in case normal/optimized one are unavailable
		inline void						SetUnoptimizedDirectory( const String&  _strPath );

		/// Get the directory for unoptimized resources, if any was specified via SetUnoptimizedDirectory(...)
		inline const String&			GetUnoptimizedDirectory( void ) const;


		/// Allows to try to load unoptimized data  if optimized one are not available ?
		inline void						SetLoadUnoptimizedFormat( bool _bLoadUnoptimize );

		/// Does the ResourceManager have to trye to load unoptimized data if optimized one are not available ?
		inline bool						DoesLoadUnoptimizedFormat( void ) const;

		/// Allows the manager to try to optimize the data, and write the result data as an optimized file on disk
		inline void						SetOptimizationOnLoad	( bool _bOptimize );

		/// Does the ResourceManager try to optimize the data on load ?
		inline bool						DoesOptimizationOnLoad	( void ) const;


		/// Specify wether the ResourceManager should automatically try to recompile a TResource whose format version is out of date
		inline void						SetAutoRecompile		( bool _bAutoRecompile );

		/// Does the ResourceManager have to recompile out of date resource ?
		inline bool						DoesAutoRecompile		( void ) const;

	 		
	protected : 

		/// NDAL : DO NEEEEEVAAAAAAAAA USE THIS !!!!!    --- std::map traversal !!!!
		const TResourceID& 				GetResourceID( const TResource& _rResource );
	

		bool										mbAutoClean;				///< Should the ResourceManager automatically try to destroy a TResource when we detroy an TResourceInstance
		bool										mbAutoRecompile;			///< Should the ResourceManager automatically try to re compile a TResource whose format is out of date
		bool										mbLoadUnoptimizedFormat;	///< Should the ResourceManager try to load unoptimized data if optimized one are not available 
		bool										mbOptimizationOnLoad;		///< Should the ResourceManager try to optimize the data, and write the result data as an optimized file on disk 

		UID											mNextUID;
		String										mstrBaseDirectory;			///< Base directory containing all resources of this type
		String										mstrUnoptimizedDirectory;	///< Base Directory containing all unoptimized resources
		TResourceID									mDefaultResourceID;			///< 
		std::map< TResourceID, TResource* >			mDicoResource;				///< Collection of available TResource, accessible via their associated TResourceID
		List< TResourceInstance* >					mlpInstances;				///< Collection of all ResourceInstance s !

	}; 



	//--------------------------------------------------------------------------
	// CTOR
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	ResourceManager< TResource, TResourceID, TResourceInstance >::ResourceManager( const String&  _strBaseDirectory )  
	:	mstrBaseDirectory		( _strBaseDirectory )
	,	mstrUnoptimizedDirectory( "" )
	,	mNextUID				( 0 )
	,	mbAutoClean				( false )
	,	mbAutoRecompile			( false )
	,	mbLoadUnoptimizedFormat	( false )
	,	mbOptimizationOnLoad	( false )
	{	
	}
	
	//--------------------------------------------------------------------------
	// DTOR
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	ResourceManager< TResource, TResourceID, TResourceInstance >::~ResourceManager( void )
	{	
		DestroyAll();
	}
	 
	//--------------------------------------------------------------------------
	// Must the ResourceManager automatically try to clean a Resource when we destroy an ResourceInstance
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	void ResourceManager< TResource, TResourceID, TResourceInstance >::SetAutoClean( bool _bAutoClean ) 
	{
		mbAutoClean = _bAutoClean;
	}		
 
	//--------------------------------------------------------------------------
	// Create the TResource associated to the given TResourceID, and Register it !
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	TResource& ResourceManager< TResource, TResourceID, TResourceInstance >::CreateAndRegister( const TResourceID& _rID ) 
	{
		TResource& rResource = Create( _rID );				
		Register( _rID , rResource ); 
		
		return rResource;				
	}
	
	
	//--------------------------------------------------------------------------
	// Destroy the TResource associated to the given TResourceID 
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	void ResourceManager< TResource, TResourceID, TResourceInstance >::DestroyAndUnregister( const TResourceID& _rID ) 
	{
		Destroy( _rID );
		Unregister( _rID ); 
	} 

	//--------------------------------------------------------------------------
	// Get the base directory containting all TResources of type TResource
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	const String& ResourceManager< TResource, TResourceID, TResourceInstance >::GetBaseDirectory( void ) const
	{
		return mstrBaseDirectory;
	}
	
	//--------------------------------------------------------------------------
	// Get the TResource associated to the given TResourceID - Create (and Register) it if it not already available !
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	TResource& ResourceManager< TResource, TResourceID, TResourceInstance >::GetOrCreate( const TResourceID& _rID )
	{
		if( IsAvailable( _rID ))
		{
			return Get( _rID );
		}
		else
		{
			TResource& rResource = Create( _rID ); 
			Register( _rID, rResource );
			
			return rResource;
		}
	}
	
	//--------------------------------------------------------------------------
	// Specify the TResource that should be used by default
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	void ResourceManager< TResource, TResourceID, TResourceInstance >::SetDefaultResource	( const TResourceID& _rID )
	{
		mDefaultResourceID = _rID;
	}
	
	//--------------------------------------------------------------------------
	// Get the TResource associated to the given TResourceID - 
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	TResource& ResourceManager< TResource, TResourceID, TResourceInstance >::Get ( const TResourceID& _rID ) 
	{
		//---------------------------------------
		// pre condition
		ASSERT_DATA_AVAILABLE();
		//--------------------------------------- 
		
		return *(mDicoResource[ _rID ]);
	}			
	
	//--------------------------------------------------------------------------
	// Create the Resource associated to the given ResourceID 
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	TResource& ResourceManager< TResource, TResourceID, TResourceInstance >::Create( const TResourceID& _rID ) 
	{ 
		TResource* pResource = new TResource( mNextUID );
		mNextUID++; // Do not worry about overflow ;)

 
		pResource->SetID( _rID );

		LOG_RESOURCE("Trying to load '%s' (%d)!\n", pResource->GetID().c_str(), pResource->GetUID() );

		bool bSuccess = pResource->Initialize( _rID );

		if( !bSuccess )
		{
			LOG_RESOURCE("-- Could not create the resource '%s'\n", _rID.c_str() );
		
			bSuccess = pResource->Initialize( mDefaultResourceID );

			MB_ASSERT( bSuccess , "--- Could not create the default resource '%s'\n", mDefaultResourceID.c_str() );
		}
		
		// FIXME : this LOG_RESOURCE will blow up when we will use a TResourceID that is not a String...
		// so when this time comes, we'll have to use a IResource::Print() to make things right
		LOG_RESOURCE("+ Created '%s'\n", _rID.c_str() );
			 
		return *pResource;
	}
	
	//--------------------------------------------------------------------------
	// Destroy the Resource associated to the given ResourceID 
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	void ResourceManager< TResource, TResourceID, TResourceInstance >::Destroy( const TResourceID& _rID ) 
	{ 
		TResource& rResource = Get( _rID ); 
		
		// FIXME : this LOG_RESOURCE will blow up when we will use a TResourceID that is not a String...
		// so when this time comes, we'll have to use a IResource::Print() to make things right
		LOG_RESOURCE("-- Destroying %s\n", _rID.c_str() );
		
		rResource.Release();
		delete &rResource;	
	}    
	
	//--------------------------------------------------------------------------
	// Destoy all registered ExtModels 
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	void ResourceManager< TResource, TResourceID, TResourceInstance >::DestroyAll	( void )
	{    
		LOG_RESOURCE( "- Destroying %d ResourceInstances\n", mlpInstances.size() );

		for( List<TResourceInstance*>::iterator itInstance = mlpInstances.begin(); itInstance != mlpInstances.end(); itInstance ++ ) 
		{
			// Already unbound by their respective IResource
			(*itInstance)->UnbindFromResource();
			delete (*itInstance);
		}  
		mlpInstances.clear();

		LOG_RESOURCE( "- Destroying %d Resources\n", mDicoResource.size() );

		for( std::map< TResourceID, TResource* >::iterator itResource= mDicoResource.begin(); itResource != mDicoResource.end(); itResource++ )
		{
			Destroy( itResource->first ); 
		}  
		mDicoResource.clear();  

	}
	
	
	//--------------------------------------------------------------------------
	// 
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	bool ResourceManager< TResource, TResourceID, TResourceInstance >::IsAvailable ( const TResourceID& _rID ) const
	{
		return mDicoResource.end() != mDicoResource.find( _rID );
	}  
 

	//--------------------------------------------------------------------------
	// Register the given TResource with its associated TResourceID, adding it into the collection of available TResource
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	void ResourceManager< TResource, TResourceID, TResourceInstance >::Register( const TResourceID& _rID, TResource& _rResource )
	{
		//---------------------------------------
		// pre condition
		ASSERT_DATA_NOT_AVAILABLE(); 
		//--------------------------------------- 
		
		mDicoResource[ _rID ] = &_rResource;
	} 
	
	//--------------------------------------------------------------------------
	// Unregister the TResource associated to the given TResourceID, removing it from the collection of available TResource - !! No memory deallocation is done !
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	void ResourceManager< TResource, TResourceID, TResourceInstance >::Unregister( const TResourceID& _rID )
	{
		//---------------------------------------
		// pre condition
		ASSERT_DATA_AVAILABLE(); 
		//--------------------------------------- 
		
		mDicoResource.erase( _rID );
	}
	
	//--------------------------------------------------------------------------
	// NDAL : DO NEEEEEVAAAAAAAAA USE THIS !!!!!    --- std::map traversal !!!!
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	void ResourceManager< TResource, TResourceID, TResourceInstance >::Unregister( TResource& _rResource )
	{
		//Unregister( GetResourceID( _rResource ) );
		// is that better ?
		Unregister( _rResource.GetID() );
	}

	//--------------------------------------------------------------------------
	// 
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	TResourceInstance& ResourceManager< TResource, TResourceID, TResourceInstance >::CreateInstance( const ResourceID& _rID )
	{
		TResource& rResource = GetOrCreate( _rID );
	
		TResourceInstance* pInstance = new TResourceInstance();
		
		pInstance->BindToResource( rResource );
		
		mlpInstances.push_back( pInstance );
		
		return *pInstance;
	}

	//--------------------------------------------------------------------------
	// 
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	void ResourceManager< TResource, TResourceID, TResourceInstance >::DestroyInstance( TResourceInstance& _rInstance )
	{
		List< TResourceInstance * >::iterator itFound = std::find( mlpInstances.begin(), mlpInstances.end() ,& _rInstance );
		
		MB_ASSERT( mlpInstances.end() != itFound, "Could not find an Instance...\n" );
		
  		TResource* pResource = _rInstance.GetResource();
  		_rInstance.UnbindFromResource();
  		
  		if( mbAutoClean )
  		{		  			
  			if( 0 == pResource->GetNbInstances() )
  			{
	  			ResourceID id = GetResourceID( *pResource );		  			
				Destroy( id );
				Unregister( id ); 		  				
  			}
  		}
  		
		delete (*itFound);
		mlpInstances.erase( itFound ); 
	}

	//--------------------------------------------------------------------------
 	// Clean every resource not currently in use !
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
 	void ResourceManager< TResource, TResourceID, TResourceInstance >::DestroyAndUnregisterAllUnusedResources( void )
 	{
 		for( std::map< TResourceID, TResource* >::iterator itResource= mDicoResource.begin(); itResource != mDicoResource.end(); itResource++ )
		{
			TResource* pResource = *( itResource->second );
			
			if( 0 == pResource->GetNbUsers() )
  			{
	  			TResourceID id = GetResourceID( *pResource );		  			
				Destroy( id );
				Unregister( id ); 		  				
  			}
		} 				
 	} 
 
	//--------------------------------------------------------------------------	
	// NDAL : DO NEEEEEVAAAAAAAAA USE THIS !!!!!    --- std::map traversal !!!!
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	const TResourceID& ResourceManager< TResource, TResourceID, TResourceInstance >::GetResourceID( const TResource& _rResource )
	{
		for( std::map<TResourceID,TResource*>::iterator itRes = mDicoResource.begin(); itRes != mDicoResource.end(); itRes ++ )
		{
			if( (*itRes).second == ( & _rResource ) )
			{
				return (*itRes).first;
			}
		}
		
		MB_ASSERT_UNREACHABLE( "TResource not found!" );
		return mDicoResource.begin()->first;
	}

	
	//--------------------------------------------------------------------------
	//
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	ULONG ResourceManager< TResource, TResourceID, TResourceInstance >::ReloadModifiedResources( void )
	{
		ULONG ulNbReloads = 0;

		for( std::map<TResourceID,TResource*>::iterator itRes = mDicoResource.begin(); itRes != mDicoResource.end(); itRes ++ )
		{
			TResource& rResource = * (*itRes).second;

			SystemTime actualLastWriteTime;

			BaseFile<WarningPolicy> file;
			String strPath = GetBaseDirectory() + rResource.GetID(); 
			
			//LOG_RESOURCE("Trying to reload '%s' (%d)!\n", rResource.GetID().c_str(), rResource.GetUID() );
			
			if( file.Open( strPath.c_str() ) )
			{
				file.GetLastWriteTime( actualLastWriteTime );
				file.Close();
				
				if(	0 < File::CompareTime( actualLastWriteTime , rResource.GetLastWriteTime() ) )
				{
					LOG_RESOURCE("A Resource has recently changed (%s)!\n", rResource.GetID().c_str() );
					rResource.Reload( actualLastWriteTime, mDefaultResourceID );

					ulNbReloads++;
				}
			}
		}	

		return ulNbReloads;
	} 
	
	//--------------------------------------------------------------------------
	// Specify a directory for unoptimized resources, in case normal/optimized one are unavailable
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	void ResourceManager< TResource, TResourceID, TResourceInstance >::SetUnoptimizedDirectory( const String&  _strPath )
	{
		mstrUnoptimizedDirectory = _strPath;
	}

	//--------------------------------------------------------------------------
	// Get the directory for unoptimized resources, if any was specified via SetUnoptimizedDirectory(...)		
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	const String& ResourceManager< TResource, TResourceID, TResourceInstance >::GetUnoptimizedDirectory( void ) const
	{
		return mstrUnoptimizedDirectory;
	}

	//--------------------------------------------------------------------------
	// Allows to try to load unoptimized data 
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	void ResourceManager< TResource, TResourceID, TResourceInstance >::SetLoadUnoptimizedFormat( bool _bLoadUnoptimize ) 
	{
		mbLoadUnoptimizedFormat = _bLoadUnoptimize;
	}		 
	//--------------------------------------------------------------------------
	// Does the ResourceManager have to recompile out of date resource ?
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	bool ResourceManager< TResource, TResourceID, TResourceInstance >::DoesLoadUnoptimizedFormat( void ) const
	{
		return mbLoadUnoptimizedFormat;
	}		  
	//--------------------------------------------------------------------------
	// Allows the manager to try to optimize the data, and write the result data as an optimized file on disk
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	void ResourceManager< TResource, TResourceID, TResourceInstance >::SetOptimizationOnLoad( bool _bOptimize ) 
	{
		mbOptimizationOnLoad = _bOptimize;
	}		 
	//--------------------------------------------------------------------------
	// Does the ResourceManager try to optimize the data on load ?
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	bool ResourceManager< TResource, TResourceID, TResourceInstance >::DoesOptimizationOnLoad( void ) const
	{
		return mbOptimizationOnLoad;
	}		  

	//--------------------------------------------------------------------------
	// Specify wether the ResourceManager should automatically try to recompile a TResource whose format version is out of date
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	void ResourceManager< TResource, TResourceID, TResourceInstance >::SetAutoRecompile( bool _bAutoRecompile ) 
	{
		mbAutoRecompile = _bAutoRecompile;
	}		 
	//--------------------------------------------------------------------------
	// Does the ResourceManager have to recompile out of date resource ?
	//--------------------------------------------------------------------------
	template< class TResource, class TResourceID , class TResourceInstance >
	bool ResourceManager< TResource, TResourceID, TResourceInstance >::DoesAutoRecompile( void ) const
	{
		return mbAutoRecompile;
	}		  
	  
}
